package solver;

import etm.core.configuration.EtmManager;
import etm.core.monitor.EtmMonitor;
import etm.core.monitor.EtmPoint;
import gurobi.GRB;
import gurobi.GRBEnv;
import gurobi.GRBException;
import gurobi.GRBLinExpr;
import gurobi.GRBModel;
import gurobi.GRBVar;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import model.BalanceObjective;
import model.Location;
import model.Machine;
import model.Problem;
import model.Process;
import model.Resource;
import model.Service;
import model.Solution;
import model.SolverEnvironment;
import util.Chronograph;

import command.IncrementalMoveProcessToMachineCommand;

public class GrbExtraLeanIPModelBuilder extends BaseExtraLeanIPModelBuilder {
	// private static final EtmMonitor etmMonitor = EtmManager.getEtmMonitor();

	HashMap<Integer, GRBVar>[] x;
	GRBVar[][] y;
	GRBVar[][] k;
	GRBVar[][] lo_cost; // load cost
	GRBVar lo_cost_remaining;
	GRBVar[][][] a;
	GRBVar[][] abs;
	GRBVar[][] ba_cost; // balance cost
	GRBVar ba_cost_remaining;
	GRBVar pm_cost; // process move cost
	GRBVar[] se; // service move cost
	GRBVar[] abs_se;
	GRBVar[] se_cost;
	GRBVar mm_cost; // machine move cost
	GRBEnv env;
	GRBModel model = null;

	public GrbExtraLeanIPModelBuilder(Problem problem,
			List<Integer> selectedProcesses, boolean verbose) {
		this(problem, selectedProcesses, verbose, 5, Long.MAX_VALUE);
	}

	public GrbExtraLeanIPModelBuilder(Problem problem,
			List<Integer> selectedProcesses, boolean verbose,
			int timeLimitInSeconds, long cutoff) {
		super(problem, selectedProcesses, verbose, timeLimitInSeconds);
		this.cutoff = cutoff;
		this.timeLimit = timeLimitInSeconds;
		try {
			env = new GRBEnv("grb_build_extra_lean_model.log");
			env = new GRBEnv();
			env.set(GRB.DoubleParam.TimeLimit, timeLimit);
			// seconds
			// env.set(GRB.IntParam.Method, 1); // dual simplex
			env.set(GRB.IntParam.Threads, 2);
			env.set(GRB.DoubleParam.MIPGap, 0);
			env.set(GRB.IntParam.OutputFlag, 0);
			// env.set(GRB.DoubleParam.Cutoff, cutoff);
			model = new GRBModel(env);
		} catch (GRBException e) {
			System.out.println("GRB Error code: " + e.getErrorCode() + " -> "
					+ e.getMessage());
		}
	}

	public boolean solve(boolean saveLP) {
		Chronograph chronos = new Chronograph();
		chronos.reset();
		Collections.sort(selectedMachines);
		Collections.sort(selectedProcesses);
		boolean flag = false;

		try {
			modelBuild();
			// ipModelAsString.append("\n");
			// addVariablesToIpModelAsString();
			// ipModelAsString.append("\n");
			// ipModelAsString.append("END\n");
			modelSolve(saveLP);
			int optimstatus = model.get(GRB.IntAttr.Status);
			if (optimstatus == GRB.Status.INF_OR_UNBD) {
				log.info("Model is infeasible or unbounded");
				// System.exit(-1);
			} else if (optimstatus == GRB.Status.INFEASIBLE) {
				log.info("Model is infeasible");
				// System.exit(-1);
			} else if (optimstatus == GRB.Status.UNBOUNDED) {
				log.info("Model is unbounded");
				// System.exit(-1);
			} else if (optimstatus == GRB.Status.TIME_LIMIT) {
				log.debug("Time limit occured");
			} else if (optimstatus == GRB.Status.OPTIMAL) {
				log.debug("Optimal solution found");
			} else {
				log.info("Unknown state Gurobi status = " + optimstatus);
				// System.exit(-1);
			}
			// ##########################################################
			log.debug("SOL COUNT = " + model.get(GRB.IntAttr.SolCount));
			// ##########################################################
			if (model.get(GRB.IntAttr.SolCount) > 0) {
				if (optimstatus == GRB.Status.OPTIMAL)
					solver_status = 0;
				else
					solver_status = 1;
				flag = true;
				printCostInfo();
			} else {
				if (optimstatus != GRB.Status.TIME_LIMIT) {
					solver_status = -1;
					log.info("Objective value= DNF Gurobi status = "
							+ optimstatus);
					// System.exit(-1);
				} else {
					solver_status = -2;
					log.debug("Objective value= DNF Gurobi status = "
							+ optimstatus);
				}
			}
		} catch (GRBException e) {
			e.printStackTrace();
		}
		chronos.lap();
		log.debug("Time to solve subproblem was: "
				+ chronos.getElapsedTimeInMilliseconds() + " milliseconds");
		return flag;
	}

	private void modelBuild() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:modelBuild");
		addVariables();

		if (includeHardConstraints[0])
			addConstraints0();
		if (includeHardConstraints[1])
			addConstraints1();
		if (includeHardConstraints[2])
			addConstraints2();
		if (includeHardConstraints[3])
			addConstraints3();
		if (includeHardConstraints[4])
			addConstraints4();
		if (includeHardConstraints[5])
			addConstraints5();

		if (includeSoftConstraints[0])
			addSoftConstraints0();
		if (problem.hasBalanceObjectives() && includeSoftConstraints[1])
			addSoftConstraints1();
		if (includeSoftConstraints[2])
			addSoftConstraints2();
		if (includeSoftConstraints[3])
			addSoftConstraints3();
		if (includeSoftConstraints[4])
			addSoftConstraints4();

		addCutOffConstraint();
		log.debug(String.format("Cutoff added =%,d", cutoff));
		// point.collect();
	}

	private void modelSolve(boolean saveLP) throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:modelSolve");
		model.update();
		if (saveLP)
			model.write("machine_reassignment_extra_lean.lp");
		model.optimize();
		model.terminate();
		// point.collect();
	}

	private long computeLoadCost() throws GRBException {
		long sum = 0;
		for (int i = 0; i < selectedMachines.size(); i++) {
			// int m = selectedMachines.get(i);
			long sum2 = 0;
			for (int r = 0; r < problem.RE; r++) {
				long r_value = Math.round(lo_cost[i][r].get(GRB.DoubleAttr.X));
				Resource aResource = problem.getResourceByIndex(r);
				sum2 += r_value * aResource.getWeightLoadCost();
			}
			sum += sum2;
		}
		sum += computeNonSelectedMachinesLoadCost();
		return sum;
	}

	private long computeBalanceCost() throws GRBException {
		long sum = 0;
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				long r_value = Math.round(ba_cost[bo_index][i]
						.get(GRB.DoubleAttr.X));
				sum += r_value * bo.weightBalanceCost;
			}
		}
		sum += computeNonSelectedMachinesBalanceCost();
		return sum;
	}

	private long computeProcessMoveCost() throws GRBException {
		return (long) Math.round(pm_cost.get(GRB.DoubleAttr.X))
				* problem.getWeightProcessMoveCost();
	}

	private long computeServiceMoveCost() throws GRBException {
		if (AS >= 1)
			return (long) Math.round(se_cost[AS - 1].get(GRB.DoubleAttr.X))
					* problem.getWeightServiceMoveCost();
		else
			return 0;
	}

	private long computeMachineMoveCost() throws GRBException {
		return (long) Math.round(mm_cost.get(GRB.DoubleAttr.X))
				* problem.getWeightMachineMoveCost();
	}

	@SuppressWarnings("unchecked")
	protected void addVariables() throws GRBException {
		generateAffectedServicesAndLocations();
		int[] variables = { 0, 0, 0, 0, 0, 0, 0 };
		int allvars = 0;
		// ipModelAsString.append("Minimize\n");
		int nop = selectedProcesses.size();
		x = new HashMap[nop];
		for (int i = 0; i < nop; i++) {
			x[i] = new HashMap<Integer, GRBVar>();
			int p = selectedProcesses.get(i);
			Process proc = problem.getProcesses().get(p);
			for (Integer machine_id : proc.getEligibleMachines()) {
				x[i].put(
						machine_id,
						model.addVar(0.0, 1.0, 0.0, GRB.BINARY,
								String.format("x%d_%d", p, machine_id)));
				variables[0]++;
				allvars++;
			}
		}

		// ##################### SPREAD VARIABLES ####################
		y = new GRBVar[AS][problem.L];
		allvars += AS * problem.L;
		for (int i = 0; i < AS; i++)
			for (Integer l : affectedLocations) {
				y[i][l] = model.addVar(0.0, 1.0, 0.0, GRB.BINARY,
						String.format("y_s%d_l%d", affectedServices.get(i), l));
				variables[1]++;
			}

		// y = new GRBVar[AS][AL];
		// allvars += AS * AL;
		// for (int i = 0; i < AS; i++)
		// for (int j = 0; j < AL; j++) {
		// y[i][j] = model.addVar(0.0, 1.0, 0.0, GRB.BINARY, String
		// .format("y_s%d_l%d", affectedServices.get(i),
		// affectedLocations.get(j)));
		// variables[1]++;
		// }

		// ##################### LOAD COST VARIABLES ####################
		int SM = selectedMachines.size();
		k = new GRBVar[SM][problem.RE];
		lo_cost = new GRBVar[SM][problem.RE];
		allvars += SM * problem.RE * 2;
		for (int i = 0; i < SM; i++) {
			int m = selectedMachines.get(i);
			for (int r = 0; r < problem.RE; r++) {
				Resource resource = problem.getResources().get(r);
				k[i][r] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
						GRB.INTEGER, String.format("k%d_%d", m, r));
				lo_cost[i][r] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						resource.getWeightLoadCost(), GRB.INTEGER,
						String.format("lc%d_%d", m, r));
				variables[2] += 2;
				// ipModelAsString.append(String.format("+ %d lc%d_%d ",
				// resource.getWeightLoadCost(), m, r));

			}
		}

		lo_cost_remaining = model.addVar(0.0, Double.POSITIVE_INFINITY, 1.0,
				GRB.INTEGER, "lo_cost_remaining");
		variables[2]++;
		allvars++;
		// ##################### BALANCE COST VARIABLES ####################
		if (problem.hasBalanceObjectives()) {
			a = new GRBVar[problem.BO][SM][2];
			allvars += problem.BO * SM * 2;
			abs = new GRBVar[problem.BO][SM];
			allvars += problem.BO * SM;
			ba_cost = new GRBVar[problem.BO][SM];
			allvars += problem.BO * SM;
			for (int bo_index = 0; bo_index < problem.BO; bo_index++) {
				BalanceObjective bo = problem.getBalanceObjectives().get(
						bo_index);
				for (int i = 0; i < SM; i++) {
					int m = selectedMachines.get(i);
					abs[bo_index][i] = model.addVar(0.0,
							Double.POSITIVE_INFINITY, 0.0, GRB.INTEGER,
							String.format("abs_bo%d_m%d", bo_index, m));
					ba_cost[bo_index][i] = model.addVar(0.0,
							Double.POSITIVE_INFINITY, bo.weightBalanceCost,
							GRB.INTEGER,
							String.format("ba_cost_bo%d_m%d", bo_index, m));
					a[bo_index][i][0] = model.addVar(0.0,
							Double.POSITIVE_INFINITY, 0.0, GRB.INTEGER, String
									.format("a_bo%d_m%d_r%d", bo_index, m,
											bo.resourcea));
					a[bo_index][i][1] = model.addVar(0.0,
							Double.POSITIVE_INFINITY, 0.0, GRB.INTEGER, String
									.format("a_bo%d_m%d_r%d", bo_index, m,
											bo.resourceb));
					variables[3] += 4;
					// ipModelAsString.append(String.format("+ %d ba_cost_bo%d_m%d ",
					// bo.weightBalanceCost, bo_index, m));
				}
			}

			ba_cost_remaining = model.addVar(0.0, Double.POSITIVE_INFINITY,
					1.0, GRB.INTEGER, "ba_cost_remaining");
			variables[3]++;
			allvars++;
		}

		// ##################### PROCESS_MOVE_COST_VARIABLE ####################
		pm_cost = model.addVar(0.0, Double.POSITIVE_INFINITY,
				problem.getWeightProcessMoveCost(), GRB.INTEGER, "pm_cost");
		// ipModelAsString.append(String.format("+ %d pm_cost ",
		// problem.getWeightProcessMoveCost()));
		variables[4]++;
		allvars++;
		// ##################### SERVICE_MOVE_COST_VARIABLES ###################
		se = new GRBVar[AS + 1];
		abs_se = new GRBVar[AS];
		se_cost = new GRBVar[AS];
		allvars += 2 * AS + 1;
		for (int i = 0; i < AS; i++) {
			se[i] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
					GRB.INTEGER,
					String.format("se_s%d", affectedServices.get(i)));
			variables[5]++;
		}
		se[AS] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0, GRB.INTEGER,
				"se_s_not_selected");
		variables[5]++;

		for (int i = 0; i < AS; i++) {
			abs_se[i] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
					GRB.INTEGER,
					String.format("abs_se_s%d", affectedServices.get(i)));
			variables[5]++;
			if (i == AS - 1) {
				se_cost[i] = model.addVar(0.0, Double.POSITIVE_INFINITY,
						problem.getWeightServiceMoveCost(), GRB.INTEGER,
						String.format("se_cost_s%d", affectedServices.get(i)));
				variables[5]++;
				// ipModelAsString.append(String.format("+ %d se_cost_s%d ",
				// problem.getWeightServiceMoveCost(),
				// affectedServices.get(i)));
			} else
				se_cost[i] = model.addVar(0.0, Double.POSITIVE_INFINITY, 0.0,
						GRB.INTEGER,
						String.format("se_cost_s%d", affectedServices.get(i)));
			variables[5]++;
		}

		// ##################### MACHINE_MOVE_COST_VARIABLE ####################
		mm_cost = model.addVar(0.0, Double.POSITIVE_INFINITY,
				problem.getWeightMachineMoveCost(), GRB.INTEGER, "mm_cost");
		variables[6]++;
		allvars++;
		// ipModelAsString.append(String.format("+ %d mm_cost ",
		// problem.getWeightMachineMoveCost()));
		// ipModelAsString.append("\n");
		// ipModelAsString.append("Subject To");
		// ipModelAsString.append("\n");
		int sum = 0;
		for (int i = 0; i < variables.length; i++) {
			sum += variables[i];
		}
		log.debug(String.format("GRBVars=%s %d %d", Arrays.toString(variables),
				sum, allvars));
		model.update();
	}

	private void addCutOffConstraint() throws GRBException {
		GRBLinExpr expr = new GRBLinExpr();
		for (int i = 0; i < selectedMachines.size(); i++) {
			for (int r = 0; r < problem.RE; r++) {
				Resource resource = problem.getResources().get(r);
				expr.addTerm(resource.getWeightLoadCost(), lo_cost[i][r]);
			}
		}
		expr.addTerm(1.0, lo_cost_remaining);

		if (problem.hasBalanceObjectives()) {
			for (int bo_index = 0; bo_index < problem.BO; bo_index++) {
				BalanceObjective bo = problem.getBalanceObjectives().get(
						bo_index);
				for (int i = 0; i < selectedMachines.size(); i++) {
					expr.addTerm(bo.weightBalanceCost, ba_cost[bo_index][i]);
				}
			}
			expr.addTerm(1.0, ba_cost_remaining);
		}

		expr.addTerm(problem.getWeightProcessMoveCost(), pm_cost);
		expr.addTerm(problem.getWeightMachineMoveCost(), mm_cost);
		if (AS >= 1) {
			expr.addTerm(problem.getWeightServiceMoveCost(), se_cost[AS - 1]);
		}
		model.addConstr(expr, GRB.LESS_EQUAL, cutoff, "cutoff contraint");
		// log.debug(String.format("cutoff constraint added! %,d\n", cutoff));
	}

	// assignment constraints
	private void addConstraints0() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints0");
		for (Integer p : selectedProcesses) {
			GRBLinExpr expr = new GRBLinExpr();
			// ipModelAsString.append(String.format("assignment_constraint_p%d: ",
			// p));
			Process aProcess = problem.getProcesses().get(p);
			for (Integer machine_id : aProcess.getEligibleMachines()) {
				expr.addTerm(1.0, getxVar(p, machine_id));
				// ipModelAsString.append(String.format("+ %.1f x%d_%d ", 1.0,
				// p,
				// machine_id));
			}
			model.addConstr(expr, GRB.EQUAL, 1.0,
					String.format("assignment_constraint_p%d", p));
			// ipModelAsString.append(String.format("= %.1f\n", 1.0));
		}
		// log.debug("Constraints0 added");
		// point.collect();
	}

	// capacity constraints
	private void addConstraints1() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints1");
		for (Integer machine_id : selectedMachines) {
			Machine aMachine = problem.getMachines().get(machine_id);
			for (int resource_id = 0; resource_id < problem.RE; resource_id++) {
				GRBLinExpr expr = new GRBLinExpr();
				// StringBuilder sb_expr = new StringBuilder();
				// sb_expr.append(String.format("capacity_constr_m%d_r%d: ",
				// machine_id, resource_id));
				int capacity = problem.C[machine_id][resource_id];
				long rhs = capacity;
				boolean termsExist = false;
				int c = 0;
				for (Integer process_id : aMachine.getMoveableProcesses()) {
					expr.addTerm(problem.R[process_id][resource_id],
							getxVar(process_id, machine_id));
					// sb_expr.append(String.format("+ %d x%d_%d ",
					// problem.R[process_id][resource_id], process_id,
					// machine_id));
					c += problem.R[process_id][resource_id];
					termsExist = true;
				}
				for (Integer process_id : current_solution
						.getProcessesForMachine(machine_id)) {
					if (!selectedProcesses.contains(process_id)) {
						rhs -= problem.R[process_id][resource_id];
					}
				}

				if (termsExist && c > rhs) {
					// ipModelAsString.append(sb_expr);
					model.addConstr(expr, GRB.LESS_EQUAL, rhs, String.format(
							"capacity_constr_m%d_r%d", machine_id, resource_id));
					// ipModelAsString.append(String.format("<= %d\n", rhs));
				}
			}
		}
		// log.debug("Constraints1 added");
		// point.collect();
	}

	// conflict constraints
	private void addConstraints2() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints2");
		for (Integer s : affectedServices) {
			Service service = problem.getServices().get(s);
			List<Integer> service_processes = service.getProcesses();
			if (service_processes.size() == 1)
				continue;
			else {
				for (int machine_id = 0; machine_id < problem.M; machine_id++) {
					GRBLinExpr expr = new GRBLinExpr();
					// StringBuilder sb_expr = new StringBuilder();
					// sb_expr.append(String.format("conflict_constr_s%d: ",
					// k));
					int rhs = 1;
					int termsExist = 0;
					for (Integer process_id : service_processes) {
						Process process = problem.getProcesses()
								.get(process_id);
						if (process.getEligibleMachines().contains(
								new Integer(machine_id))) {
							if (getxVar(process_id, machine_id) == null) {
								if (current_solution.getMachineIdForProcessId(
										process_id).intValue() == machine_id)
									rhs--;
							} else {
								expr.addTerm(1.0,
										getxVar(process_id, machine_id));
								// sb_expr.append(String.format("+ %.1f x%d_%d ",
								// 1.0, process_id, machine_id));
								termsExist++;
							}
						} else if (current_solution.getMachineIdForProcessId(
								process_id).intValue() == machine_id) {
							rhs--;
						}
					}
					if (termsExist > rhs) {
						// ipModelAsString.append(sb_expr);
						model.addConstr(expr, GRB.LESS_EQUAL, rhs, String
								.format("conflict_constr_s%d_m%d", s,
										machine_id));
						// ipModelAsString.append(String.format("<= %d\n",
						// rhs));
					}
				}
			}
		}
		// log.debug("Constraints2 added");
		// point.collect();
	}

	// spread constraints
	private void addConstraints3() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints3");
		int k = 0;
		// log.debug("AFFECTED SERVICES " + affectedServices);
		for (Integer s : affectedServices) {
			Service service = problem.getServices().get(s);
			if (service.getSpreadMin() <= 1) {
				k++;
				continue;
			}
			// log.debug(service.toString());
			// log.debug("Current spread " +
			// current_solution.getSpreadForService(s));
			GRBLinExpr expr = new GRBLinExpr();
			// StringBuilder sb_expr = new StringBuilder();
			// sb_expr.append(String.format("spread_constr_s%d ", k));
			int spread_min = service.getSpreadMin();
			for (Location location : problem.getLocations()) {
				GRBLinExpr expr1 = new GRBLinExpr();
				// StringBuilder sb_expr1 = new StringBuilder();
				// sb_expr1.append(String.format("spread_constr_s%d_l%d_a ", k,
				// location.getId()));

				GRBLinExpr expr2 = new GRBLinExpr();
				// StringBuilder sb_expr2 = new StringBuilder();
				// sb_expr2.append(String.format("spread_constr_s%d_l%d_b ", k,
				// location.getId()));

				boolean exist = false;
				int rhs = 0;
				for (Integer m : location.getMachines()) {
					for (Integer p : service.getProcesses()) {
						GRBVar xVar = getxVar(p, m);
						if (xVar != null) {
							exist = true;
							expr1.addTerm(1.0, xVar);
							// sb_expr1.append(String.format("+ %.1f x%d_%d ",
							// 1.0, p, m));
							expr2.addTerm(1.0, xVar);
							// sb_expr2.append(String.format("+ %.1f x%d_%d ",
							// 1.0, p, m));
							// log.debug(String.format("Variable X%d_%d added",
							// p,m));
						} else if (current_solution.getMachineIdForProcessId(p)
								.intValue() == m.intValue()) {
							// log.debug(String
							// .format("Process %d from service %d exists in machine %d location %d",
							// p, s, m, location.getId()));
							rhs--;
						}
					}
				}

				if (!exist && rhs == 0) {
					// log.debug(String
					// .format("After analyzing service %d and location %d %s spreadmin is %d",
					// s, location.getId(),
					// location.getMachines(), spread_min));
					continue;
				}
				if (!exist && rhs < 0) {
					spread_min--;
					// log.debug(String
					// .format("After analyzing service %d and location %d %s spreadmin becomes %d",
					// s, location.getId(),
					// location.getMachines(), spread_min));
					continue;
				}

				expr1.addTerm(-1.0, y[k][location.getId()]);
				// sb_expr1.append(String.format("+ %.1f y%d_%d ", -1.0, k,
				// location.getId()));

				expr2.addTerm(-location.getMachines().size(),
						y[k][location.getId()]);
				// sb_expr2.append(String.format("+ %d y%d_%d ", -location
				// .getMachines().size(), k, location.getId()));

				model.addConstr(
						expr1,
						GRB.GREATER_EQUAL,
						rhs,
						String.format("spread_constr_s%d_l%d_a", s,
								location.getId()));
				// sb_expr1.append(String.format(">= %d\n", rhs));
				// ipModelAsString.append(sb_expr1.toString());

				model.addConstr(
						expr2,
						GRB.LESS_EQUAL,
						rhs,
						String.format("spread_constr_s%d_l%d_b", s,
								location.getId()));
				// sb_expr2.append(String.format("<= %d\n", rhs));
				// ipModelAsString.append(sb_expr2.toString());

				expr.addTerm(1.0, y[k][location.getId()]);
				// sb_expr.append(String.format("+ %.1f y%d_%d ", 1.0, k,
				// location.getId()));
			}

			model.addConstr(expr, GRB.GREATER_EQUAL, spread_min,
					String.format("spread_constr_s%d", s));
			// sb_expr.append(String.format(">= %d\n", spread_min));
			k++;
			// ipModelAsString.append(sb_expr.toString());
		}
		// log.debug("Constraints3 added");
		// point.collect();
	}

	// Dependency constraints
	private void addConstraints4() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints4");
		Chronograph chronos = new Chronograph();
		chronos.reset();
		int counters[] = { 0, 0, 0 };
		Map<String, Integer> type4constraints = new HashMap<String, Integer>();
		List<String> sb_nList = new ArrayList<String>();
		for (Integer sa_index : offendedServices) {
			Service servicea = problem.getServices().get(sa_index);
			for (Integer pa_index : servicea.getProcesses()) {
				for (Integer neighborhood_id : offendedNeighborhoods) {
					int r = 0;
					List<GRBVar> plusXvarsList = new ArrayList<GRBVar>();
					boolean plusXExist = false;
					String key1 = String.format("%d_%d", pa_index,
							neighborhood_id);
					boolean plusXexistsHavingValue1InCurrentSolution = false;
					if (selected_XVARS_per_process_neighborhood
							.containsKey(key1)) {
						for (String xvar_s : selected_XVARS_per_process_neighborhood
								.get(key1)) {
							String s[] = xvar_s.split("_");
							int pa_id = Integer.parseInt(s[0]);
							int m_id = Integer.parseInt(s[1]);
							GRBVar xVar = getxVar(pa_id, m_id);
							plusXvarsList.add(xVar);
							plusXExist = true;
							counters[0]++;
							if (current_solution.getMachineIdForProcessId(
									pa_index).intValue() == m_id)
								plusXexistsHavingValue1InCurrentSolution = true;
						}
					}
					if (current_solution.getNeighBorhoodIdForProcess(pa_index) == neighborhood_id) {
						if (plusXexistsHavingValue1InCurrentSolution == false)
							r = -1;
					}
					if ((r == 0) && (plusXExist == false))
						continue;

					for (Integer sb_index : servicea.getDependsOn()) {
						String key2 = String.format("%d_%d", sb_index,
								neighborhood_id);
						if (sb_nList.contains(key2) && plusXExist == false)
							continue;
						int rhs = r;
						List<GRBVar> minusXvarsList = new ArrayList<GRBVar>();
						boolean minusXExists = false;
						int c = current_solution.serviceProcessesPerNeighborhood[sb_index][neighborhood_id];
						if (selected_XVARS_per_service_neighborhood
								.containsKey(key2)) {
							for (String xvar_s : selected_XVARS_per_service_neighborhood
									.get(key2)) {
								String s[] = xvar_s.split("_");
								int pa_id = Integer.parseInt(s[0]);
								int m_id = Integer.parseInt(s[1]);
								GRBVar xVar = getxVar(pa_id, m_id);
								minusXvarsList.add(xVar);
								minusXExists = true;
								counters[1]++;
								if (current_solution.getMachineIdForProcessId(
										pa_id).intValue() == m_id)
									c--;
							}
						}
						rhs += c;
						if (!plusXExist && minusXExists && rhs >= 0)
							continue;
						if ((plusXExist || minusXExists) && rhs <= 0) {
							counters[2]++;
							StringBuilder sb = new StringBuilder();
							for (GRBVar xVar : plusXvarsList) {
								sb.append(" + "
										+ xVar.get(GRB.StringAttr.VarName));
							}
							for (GRBVar xVar : minusXvarsList) {
								sb.append(" - "
										+ xVar.get(GRB.StringAttr.VarName));
							}
							if (type4constraints.containsKey(sb.toString())) {
								int existing_rhs = type4constraints.get(sb
										.toString());
								if (existing_rhs <= rhs) {
									// log.debug(String.format(
									// "Constraint %s <= %d skipped",
									// sb.toString(), rhs));
									continue;
								} else {
									type4constraints.put(sb.toString(), rhs);
									// log.debug(String
									// .format("RHS for %s has been replaced from %d to %d",
									// sb.toString(),
									// existing_rhs, rhs));
								}
							} else {
								type4constraints.put(sb.toString(), rhs);
							}

							GRBLinExpr expr = new GRBLinExpr();
							for (GRBVar xVar : plusXvarsList) {
								expr.addTerm(1.0, xVar);
							}
							for (GRBVar xVar : minusXvarsList) {
								expr.addTerm(-1.0, xVar);
							}
							// ipModelAsString.append(sb_expr.toString());
							model.addConstr(expr, GRB.LESS_EQUAL, rhs, String
									.format("depends_constr_sa%d_sb%d_p%d_n%d",
											sa_index, sb_index, pa_index,
											neighborhood_id));
							if (plusXExist == false)
								sb_nList.add(key2);
							// ipModelAsString.append(String
							// .format("<= %d\n", rhs));
						}
					}
				}
			}
		}
		chronos.lap();
		// log.debug(String.format("Time to build Constraint4 was %,d ms %s",
		// chronos.getElapsedTimeInMilliseconds(),
		// Arrays.toString(counters)));
		// log.debug("Constraints4 added");
		// point.collect();
	}

	boolean fixedProcessExistsOfServiceInNeighborhood(Integer sa_index,
			Integer nei) {
		Service sa = problem.getServices().get(sa_index);
		for (Integer p : sa.getProcesses()) {
			// Process aProcess = problem.getProcesses().get(p);
			if (selectedProcesses.contains(p))
				if (current_solution.getNeighBorhoodIdForProcess(p) == nei
						.intValue()) {
					return false;
				}
		}
		return true;
	}

	// Transient constraints
	private void addConstraints5() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addConstraints5");
		for (int i = 0; i < problem.TRE.length; i++) {
			int r = problem.TRE[i];
			for (Integer m : selectedMachines) {
				GRBLinExpr expr = new GRBLinExpr();
				// StringBuilder sb_expr = new StringBuilder();
				// sb_expr.append(String
				// .format("transient_constr_m%d_r%d: ", m, r));
				// StringBuilder explain = new StringBuilder();
				boolean termsExist = false;
				int capacity = problem.RC[m][i];
				long c = 0;
				for (Integer p : current_solution.getProcessesForMachine(m)) {
					if (initial_solution.getMachineIdForProcessId(p).intValue() == m
							.intValue())
						continue;
					if (selectedProcesses.contains(p))
						continue;
					capacity -= problem.R[p][r];
				}
				for (Integer p : selectedProcesses) {
					if (initial_solution.getMachineIdForProcessId(p).intValue() == m
							.intValue())
						continue;
					GRBVar xVar = getxVar(p, m);
					if (xVar != null) {
						expr.addTerm(problem.R[p][r], xVar);
						// sb_expr.append(String.format(" + %d x%d_%d ",
						// problem.R[p][r], p, m));
						c += problem.R[p][r];
						termsExist = true;
					}
				}

				if (termsExist & c > capacity) {
					// ipModelAsString.append(sb_expr);
					model.addConstr(expr, GRB.LESS_EQUAL, capacity,
							String.format("transient_constr_m%d_r%d", m, r));
					// ipModelAsString.append(String.format("<= %d\n",
					// capacity));
					// System.out.println(explain.toString());
				}
			}
		}
		// log.debug("Constraints5 added");
		// point.collect();
	}

	// load cost GRB
	private void addSoftConstraints0() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addSoftConstraints0");
		for (int r = 0; r < problem.RE; r++) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				int m = selectedMachines.get(i);
				List<GRBVar> xvarsList = new ArrayList<GRBVar>();
				List<Integer> resourceCapacityList = new ArrayList<Integer>();
				for (Integer p : selectedProcesses) {
					GRBVar xVar = getxVar(p, m);
					if (xVar != null) {
						xvarsList.add(xVar);
						resourceCapacityList.add(problem.R[p][r]);
					}
				}
				if (!xvarsList.isEmpty()) {
					int safety_capacity = problem.getMachines().get(m)
							.getSafetyCapacityFor("r" + r);
					double rhs1 = safety_capacity;
					int rhs2 = 0;
					int rhs3 = 0;
					GRBLinExpr expr1 = new GRBLinExpr();
					// StringBuilder sb_expr1 = new StringBuilder();
					// sb_expr1.append(String.format("load_cost_constr_m%d_r%d_a: ",
					// m, r));
					GRBLinExpr expr2 = new GRBLinExpr();
					// StringBuilder sb_expr2 = new StringBuilder();
					// sb_expr2.append(String.format("load_cost_constr_m%d_r%d_b: ",
					// m, r));
					GRBLinExpr expr3 = new GRBLinExpr();
					// StringBuilder sb_expr3 = new StringBuilder();
					// sb_expr3.append(String.format("load_cost_constr_m%d_r%d_c: ",
					// m, r));
					int ri = 0;
					for (GRBVar xVar : xvarsList) {
						expr1.addTerm(resourceCapacityList.get(ri) / 2.0, xVar);
						// sb_expr1.append(String.format(" + %.1f x%d_%d ",
						// problem.R[p][r] / 2.0, p, m));

						expr2.addTerm(-resourceCapacityList.get(ri), xVar);
						// sb_expr2.append(String.format(" + %d x%d_%d ",
						// -problem.R[p][r], p, m));

						expr3.addTerm(resourceCapacityList.get(ri), xVar);
						// sb_expr3.append(String.format(" + %d x%d_%d ",
						// problem.R[p][r], p, m));
						ri++;
					}
					for (Integer p : current_solution.getProcessesForMachine(m)) {
						if (!selectedProcesses.contains(p)) {
							rhs1 -= problem.R[p][r] / 2.0;
							rhs2 += problem.R[p][r];
							rhs3 -= problem.R[p][r];
						}
					}
					expr1.addTerm(0.5, k[i][r]);
					// sb_expr1.append(String.format(" + %.1f k%d_%d ", 0.5, m,
					// r));

					expr1.addTerm(-1.0, lo_cost[i][r]);
					// sb_expr1.append(String.format(" + %.1f lc%d_%d ", -1.0,
					// m,
					// r));

					model.addConstr(expr1, GRB.EQUAL, rhs1,
							String.format("load_cost_constr_m%d_r%d_a", m, r));
					// sb_expr1.append(String.format("= %.1f\n", rhs1));
					// ipModelAsString.append(sb_expr1);

					expr2.addTerm(1.0, k[i][r]);
					// sb_expr2.append(String.format(" + %.1f k%d_%d ", 1.0, m,
					// r));

					model.addConstr(expr2, GRB.GREATER_EQUAL, rhs2,
							String.format("load_cost_constr_m%d_r%d_b", m, r));
					// sb_expr2.append(String.format(">= %d\n", rhs2));
					// ipModelAsString.append(sb_expr2);

					expr3.addTerm(1.0, k[i][r]);
					// sb_expr3.append(String.format(" + %.1f k%d_%d ", 1.0, m,
					// r));

					model.addConstr(expr3, GRB.GREATER_EQUAL, rhs3,
							String.format("load_cost_constr_m%d_r%d_c", m, r));
					// sb_expr3.append(String.format(">= %d\n", rhs3));
					// ipModelAsString.append(sb_expr3);
				}
			}
		}

		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1.0, lo_cost_remaining);
		long lo_cost_nsm = computeNonSelectedMachinesLoadCost();
		model.addConstr(expr, GRB.EQUAL, lo_cost_nsm,
				"load_cost_constr_not_selected_machines");
		// log.debug("Soft Constraints0 added");
		// point.collect();
	}

	// balance cost
	private void addSoftConstraints1() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addSoftConstraints1");
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				int m = selectedMachines.get(i);
				boolean exists = false;
				GRBLinExpr expr1a = new GRBLinExpr();
				// StringBuilder sb_expr1a = new StringBuilder();
				// sb_expr1a.append(String.format(
				// "balance_cost_constr_bo%d_m%d_r%d_a%d_%d: ", bo_index,
				// m, bo.resourcea, m, bo.resourcea));

				GRBLinExpr expr1b = new GRBLinExpr();
				// StringBuilder sb_expr1b = new StringBuilder();
				// sb_expr1b.append(String.format(
				// "balance_cost_constr_bo%d_m%d_r%d_a%d_%d: ", bo_index,
				// m, bo.resourceb, m, bo.resourceb));

				int capacity_a = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourcea);
				int capacity_b = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourceb);
				int rhs_a = capacity_a;
				int rhs_b = capacity_b;
				for (Integer p : selectedProcesses) {
					GRBVar xVar = getxVar(p, m);
					if (xVar != null) {
						exists = true;
						expr1a.addTerm(problem.R[p][bo.resourcea], xVar);
						// sb_expr1a.append(String.format(" + %d x%d_%d ",
						// problem.R[p][bo.resourcea], p, m));

						expr1b.addTerm(problem.R[p][bo.resourceb], xVar);
						// sb_expr1b.append(String.format(" + %d x%d_%d ",
						// problem.R[p][bo.resourceb], p, m));

					} else {
						if (current_solution.getMachineIdForProcessId(p)
								.intValue() == m) {
							rhs_a -= problem.R[p][bo.resourcea];
							rhs_b -= problem.R[p][bo.resourceb];
						}
					}
				}
				for (Integer p : current_solution.getProcessesForMachine(m)) {
					if (!selectedProcesses.contains(p)) {
						rhs_a -= problem.R[p][bo.resourcea];
						rhs_b -= problem.R[p][bo.resourceb];
					}
				}
				if (exists) {
					expr1a.addTerm(1.0, a[bo_index][i][0]);
					// sb_expr1a.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// 1.0, bo_index, m, 0));

					expr1b.addTerm(1.0, a[bo_index][i][1]);
					// sb_expr1b.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// 1.0, bo_index, m, 1));

					model.addConstr(expr1a, GRB.EQUAL, rhs_a, String.format(
							"balance_cost_constr_bo%d_m%d_r%d_a%d_%d",
							bo_index, m, bo.resourcea, m, bo.resourcea));
					// sb_expr1a.append(String.format("= %d\n", rhs_a));
					// ipModelAsString.append(sb_expr1a);

					model.addConstr(expr1b, GRB.EQUAL, rhs_b, String.format(
							"balance_cost_constr_bo%d_m%d_r%d_a%d_%d",
							bo_index, m, bo.resourceb, m, bo.resourceb));
					// sb_expr1b.append(String.format("= %d\n", rhs_b));
					// ipModelAsString.append(sb_expr1b);

					GRBLinExpr expr2 = new GRBLinExpr();
					// StringBuilder sb_expr2 = new StringBuilder();
					// sb_expr2.append(String.format(
					// "balance_cost_constr_bo%d_m%d_a: ", bo_index, m));

					GRBLinExpr expr3 = new GRBLinExpr();
					// StringBuilder sb_expr3 = new StringBuilder();
					// sb_expr3.append(String.format(
					// "balance_cost_constr_bo%d_m%d_b: ", bo_index, m));

					GRBLinExpr expr4 = new GRBLinExpr();
					// StringBuilder sb_expr4 = new StringBuilder();
					// sb_expr4.append(String.format(
					// "balance_cost_constr_bo%d_m%d_c: ", bo_index, m));

					expr2.addTerm(bo.target / 2.0, a[bo_index][i][0]);
					// sb_expr2.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// bo.target / 2.0, bo_index, m, 0));

					expr2.addTerm(-0.5, a[bo_index][i][1]);
					// sb_expr2.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// -0.5, bo_index, m, 1));

					expr2.addTerm(0.5, abs[bo_index][i]);
					// sb_expr2.append(String.format(" + %.1f abs_bo%d_m%d ",
					// 0.5,
					// bo_index, m));

					expr2.addTerm(-1.0, ba_cost[bo_index][i]);
					// sb_expr2.append(String.format(" + %.1f ba_cost_bo%d_m%d ",
					// -1.0, bo_index, m));

					model.addConstr(expr2, GRB.EQUAL, 0.0, String.format(
							"balance_cost_constr_bo%d_m%d_a", bo_index, m));
					// sb_expr2.append(String.format("= %.1f\n", 0.0));
					// ipModelAsString.append(sb_expr2);

					expr3.addTerm(1.0, abs[bo_index][i]);
					// sb_expr3.append(String.format(" + %.1f abs_bo%d_m%d ",
					// 1.0,
					// bo_index, m));

					expr3.addTerm(bo.target, a[bo_index][i][0]);
					// sb_expr3.append(String.format(" + %d a_bo%d_m%d_r%d ",
					// bo.target, bo_index, m, 0));

					expr3.addTerm(-1.0, a[bo_index][i][1]);
					// sb_expr3.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// -1.0, bo_index, m, 1));

					model.addConstr(expr3, GRB.GREATER_EQUAL, 0.0, String
							.format("balance_cost_constr_bo%d_m%d_b", bo_index,
									m));
					// sb_expr3.append(String.format(">= %.1f\n", 0.0));
					// ipModelAsString.append(sb_expr3);

					expr4.addTerm(1.0, abs[bo_index][i]);
					// sb_expr4.append(String.format(" + %.1f abs_bo%d_m%d ",
					// 1.0,
					// bo_index, m));

					expr4.addTerm(-bo.target, a[bo_index][i][0]);
					// sb_expr4.append(String.format(" + %d a_bo%d_m%d_r%d ",
					// -bo.target, bo_index, m, 0));

					expr4.addTerm(1.0, a[bo_index][i][1]);
					// sb_expr4.append(String.format(" + %.1f a_bo%d_m%d_r%d ",
					// 1.0, bo_index, m, 1));

					model.addConstr(expr4, GRB.GREATER_EQUAL, 0.0, String
							.format("balance_cost_constr_bo%d_m%d_c", bo_index,
									m));
					// sb_expr4.append(String.format(">= %.1f\n", 0.0));
					// ipModelAsString.append(sb_expr4);
				}
			}
			bo_index++;
		}

		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1.0, ba_cost_remaining);
		long ba_cost_nsm = computeNonSelectedMachinesBalanceCost();
		model.addConstr(expr, GRB.EQUAL, ba_cost_nsm,
				"balance_cost_constr_not_selected_machines");
		// log.debug("Soft Constraints1 added");
		// point.collect();
	}

	// process move cost
	private void addSoftConstraints2() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addSoftConstraints2");
		GRBLinExpr expr = new GRBLinExpr();
		// ipModelAsString.append(String.format("process_move_cost: "));
		int sum = 0;
		for (int p = 0; p < problem.P; p++) {
			int pmc = problem.getProcesses().get(p).getProcessMoveCost();
			if (selectedProcesses.contains(p)) {
				for (int m = 0; m < problem.M; m++) {
					GRBVar xVar = getxVar(p, m);
					if (xVar == null) {
						if (initial_solution.getMachineIdForProcessId(p)
								.intValue() == m) {
							sum += pmc;
						}
					} else {
						if ((initial_solution.getMachineIdForProcessId(p)
								.intValue() == m)) {
							sum += pmc;
							expr.addTerm(-pmc, xVar);
							// ipModelAsString.append(String.format(
							// " + %d x%d_%d ", -pmc, p, m));
						} else {
							expr.addTerm(pmc, xVar);
							// ipModelAsString.append(String.format(
							// " + %d x%d_%d ", pmc, p, m));
						}
					}
				}
			} else {
				if (initial_solution.getMachineIdForProcessId(p).intValue() != current_solution
						.getMachineIdForProcessId(p).intValue()) {
					sum += pmc * 2;
				}
			}
		}
		expr.addTerm(-2.0, pm_cost);
		// ipModelAsString
		// .append(String.format(" + %.1f pm_cost ", -2.0, pm_cost));
		model.addConstr(expr, GRB.EQUAL, -sum, "process_move_cost");
		// ipModelAsString.append(String.format("= %d\n", -sum));
		// log.debug("Soft Constraints2 added");
		// point.collect();
	}

	// service move cost
	private void addSoftConstraints3() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addSoftConstraints3");
		if (problem.S <= 1)
			return;
		if (affectedServices.size() == 0)
			return;
		else if (affectedServices.size() == 1) {
			// GRBLinExpr expr0 = new GRBLinExpr();
			// expr0.addTerm(1.0, se[AS-1]);
			// long rhs0 = computeNonSelectedMachinesServiceMoveCost();
			// log.error("NSM SMC =" + rhs0);
			// model.addConstr(expr0, GRB.EQUAL, rhs0,
			// "service_move_cost_constr_not_selected");

			return;
		}
		// log.debug("AS=" + AS);
		GRBLinExpr expr0 = new GRBLinExpr();
		expr0.addTerm(1.0, se[AS]);
		long rhs0 = computeNonSelectedMachinesServiceMoveCost();
		model.addConstr(expr0, GRB.EQUAL, rhs0,
				"service_move_cost_constr_not_selected");
		for (Integer s : affectedServices) {
			GRBLinExpr expr = new GRBLinExpr();
			// StringBuilder sb_expr = new StringBuilder();
			// sb_expr.append(String.format("service_move_cost_constr_s%d: ",
			// s));
			boolean exists = false;
			Service service = problem.getServices().get(s);
			int rhs = -service.getProcesses().size();
			// System.out.printf("Service %d processes[%s]\n", s,
			// service.getProcesses());
			for (Integer p : service.getProcesses()) {
				if (selectedProcesses.contains(p)) {
					Process aProcess = problem.getProcesses().get(p);
					for (Integer m : aProcess.getEligibleMachines()) {
						GRBVar xVar = getxVar(p, m);
						exists = true;
						if (initial_solution.getMachineIdForProcessId(p)
								.intValue() == m.intValue()) {
							expr.addTerm(-1.0, xVar);
							// sb_expr.append(String.format(" + %.1f x%d_%d ",
							// -1.0, p, m));
						} else {
							expr.addTerm(1.0, xVar);
							// sb_expr.append(String.format(" + %.1f x%d_%d ",
							// 1.0, p, m));
						}
					}
				} else {
					if (initial_solution.getMachineIdForProcessId(p).intValue() != current_solution
							.getMachineIdForProcessId(p).intValue())
						rhs--;
					else
						rhs++;
				}
			}
			if (exists) {
				int index_of_service = affectedServices.indexOf(s);
				expr.addTerm(-2.0, se[index_of_service]);
				// sb_expr.append(String.format(" + %.1f se_s%d ", -2.0, s));

				model.addConstr(expr, GRB.EQUAL, rhs,
						String.format("service_move_cost_constr_s%d", s));
				// sb_expr.append(String.format("= %d\n", rhs));
				// ipModelAsString.append(sb_expr);
			}
		}
		int first_service_id = affectedServices.get(0);
		int second_service_id = affectedServices.get(1);
		GRBLinExpr expr1 = new GRBLinExpr();
		// StringBuilder sb_expr1 = new StringBuilder();
		// sb_expr1.append(String.format(
		// "service_move_cost_constr_max_s%d_a: ", first_service_id));

		expr1.addTerm(0.5, se[0]);
		// sb_expr1.append(String.format(" + %.1f se_s%d ", 0.5,
		// first_service_id));

		expr1.addTerm(0.5, se[1]);
		// sb_expr1.append(String.format(" + %.1f se_s%d ", 0.5,
		// second_service_id));

		expr1.addTerm(0.5, abs_se[0]);
		// sb_expr1.append(String.format(" + %.1f abs_se_s%d ", 0.5,
		// first_service_id));

		expr1.addTerm(-1.0, se_cost[0]);
		// sb_expr1.append(String.format(" + %.1f se_cost_s%d ", -1.0,
		// first_service_id));

		model.addConstr(expr1, GRB.EQUAL, 0, String.format(
				"service_move_cost_constr_max_s%d_%d_a", first_service_id,
				second_service_id));
		// sb_expr1.append(String.format("= %d\n", 0));
		// ipModelAsString.append(sb_expr1);

		GRBLinExpr expr2 = new GRBLinExpr();
		// StringBuilder sb_expr2 = new StringBuilder();
		// sb_expr2.append(String.format(
		// "service_move_cost_constr_max_s%d_b: ", first_service_id));

		expr2.addTerm(1.0, abs_se[0]);
		// sb_expr2.append(String.format(" + %.1f abs_se_s%d ", 1.0,
		// first_service_id));

		expr2.addTerm(1.0, se[0]);
		// sb_expr2.append(String.format(" + %.1f se_s%d ", 1.0,
		// first_service_id));

		expr2.addTerm(-1.0, se[1]);
		// sb_expr2.append(String.format(" + %.1f se_s%d ", -1.0,
		// second_service_id));

		model.addConstr(expr2, GRB.GREATER_EQUAL, 0, String.format(
				"service_move_cost_constr_max_s%d_s%d_b", first_service_id,
				second_service_id));
		// sb_expr2.append(String.format(">= %d\n", 0));
		// ipModelAsString.append(sb_expr2);

		GRBLinExpr expr3 = new GRBLinExpr();
		// StringBuilder sb_expr3 = new StringBuilder();
		// sb_expr3.append(String.format(
		// "service_move_cost_constr_max_s%d_c: ", first_service_id));

		expr3.addTerm(1.0, abs_se[0]);
		// sb_expr3.append(String.format(" + %.1f abs_se_s%d ", 1.0,
		// first_service_id));

		expr3.addTerm(-1.0, se[0]);
		// sb_expr3.append(String.format(" + %.1f se_s%d ", -1.0,
		// first_service_id));

		expr3.addTerm(1.0, se[1]);
		// sb_expr3.append(String.format(" + %.1f se_s%d ", 1.0,
		// second_service_id));

		model.addConstr(expr3, GRB.GREATER_EQUAL, 0, String.format(
				"service_move_cost_constr_max_s%d_s%d_c", first_service_id,
				second_service_id));
		// sb_expr3.append(String.format(">= %d\n", 0));
		// ipModelAsString.append(sb_expr3);

		for (int i = 2; i <= AS; i++) {
			int iminus2_service_id = affectedServices.get(i - 2);
			int iminus1_service_id = affectedServices.get(i - 1);
			String i_service_id;
			if (i < AS) {
				i_service_id = String.valueOf(affectedServices.get(i));
			} else {
				i_service_id = "not_selected_services";
			}

			GRBLinExpr expr4 = new GRBLinExpr();
			// StringBuilder sb_expr4 = new StringBuilder();
			// sb_expr4.append(String.format(
			// "service_move_cost_constr_max_s%d_a: ", i_service_id));

			expr4.addTerm(0.5, se_cost[i - 2]);
			// sb_expr4.append(String.format(" + %.1f se_cost_s%d ", 0.5,
			// iminus2_service_id));

			expr4.addTerm(0.5, se[i]);
			// sb_expr4.append(String.format(" + %.1f se_s%d ", 0.5,
			// i_service_id));

			expr4.addTerm(0.5, abs_se[i - 1]);
			// sb_expr4.append(String.format(" + %.1f abs_se_s%d ", 0.5,
			// iminus1_service_id));

			expr4.addTerm(-1.0, se_cost[i - 1]);
			// sb_expr4.append(String.format(" + %.1f se_cost_s%d ", -1.0,
			// iminus1_service_id));

			model.addConstr(expr4, GRB.EQUAL, 0, String.format(
					"service_move_cost_constr_max_s%d_s%s_a",
					iminus1_service_id, i_service_id));
			// sb_expr4.append(String.format("= %d\n", 0));
			// ipModelAsString.append(sb_expr4);

			GRBLinExpr expr5 = new GRBLinExpr();
			// StringBuilder sb_expr5 = new StringBuilder();
			// sb_expr5.append(String.format(
			// "service_move_cost_constr_max_s%d_b: ", i_service_id));

			expr5.addTerm(1.0, abs_se[i - 1]);
			// sb_expr5.append(String.format(" + %.1f abs_se_s%d ", 1.0,
			// iminus1_service_id));

			expr5.addTerm(1.0, se_cost[i - 2]);
			// sb_expr5.append(String.format(" + %.1f se_cost_s%d ", 1.0,
			// iminus2_service_id));

			expr5.addTerm(-1.0, se[i]);
			// sb_expr5.append(String.format(" + %.1f se_s%d ", -1.0,
			// i_service_id));

			model.addConstr(expr5, GRB.GREATER_EQUAL, 0, String.format(
					"service_move_cost_constr_max_s%d_s%s_b",
					iminus1_service_id, i_service_id));
			// sb_expr5.append(String.format(">= %d\n", 0));
			// ipModelAsString.append(sb_expr5);

			GRBLinExpr expr6 = new GRBLinExpr();
			// StringBuilder sb_expr6 = new StringBuilder();
			// sb_expr6.append(String.format(
			// "service_move_cost_constr_max_s%d_c: ", i_service_id));

			expr6.addTerm(1.0, abs_se[i - 1]);
			// sb_expr6.append(String.format(" + %.1f abs_se_s%d ", 1.0,
			// iminus1_service_id));

			expr6.addTerm(-1.0, se_cost[i - 2]);
			// sb_expr6.append(String.format(" + %.1f se_cost_s%d ", -1.0,
			// iminus2_service_id));

			expr6.addTerm(1.0, se[i]);
			// sb_expr6.append(String.format(" + %.1f se_s%d ", 1.0,
			// i_service_id));

			model.addConstr(expr6, GRB.GREATER_EQUAL, 0, String.format(
					"service_move_cost_constr_max_s%d_s%s_c",
					iminus1_service_id, i_service_id));
			// sb_expr6.append(String.format(">= %d\n", 0));
			// ipModelAsString.append(sb_expr6);

		}
		// log.debug("Soft Constraints3 added");
		// point.collect();
	}

	// machine move cost
	private void addSoftConstraints4() throws GRBException {
		// EtmPoint point = etmMonitor
		// .createPoint("GrbExtraLeanIPModelBuilder:addSoftConstraints4");
		GRBLinExpr expr = new GRBLinExpr();
		// ipModelAsString.append(String.format("machine_move_cost: "));
		int rhs = 0;
		for (Integer p : selectedProcesses) {
			Process selectedProcess = problem.getProcesses().get(p);
			int im = initial_solution.getMachineIdForProcessId(p);
			Machine aMachine = problem.getMachines().get(im);
			for (Integer m : selectedProcess.getEligibleMachines()) {
				if (im == m.intValue() || aMachine.getMachineMoveCost(m) == 0)
					continue;
				GRBVar xVar = getxVar(p, m);
				expr.addTerm(aMachine.getMachineMoveCost(m), xVar);
				// ipModelAsString.append(String.format(
				// " + %d x%d_%d ",
				// aMachine.getMachineMoveCost(mdest), p,
				// mdest));
			}
		}
		for (int p = 0; p < problem.P; p++) {
			if (selectedProcesses.contains(new Integer(p)))
				continue;
			int im = initial_solution.getMachineIdForProcessId(p);
			int cm = current_solution.getMachineIdForProcessId(p);
			if (im != cm) {
				Machine aMachine = problem.getMachines().get(im);
				rhs -= aMachine.getMachineMoveCost(cm);
			}
		}
		expr.addTerm(-1.0, mm_cost);
		// ipModelAsString.append(String.format(" + %.1f mm_cost ", -1.0));
		model.addConstr(expr, GRB.EQUAL, rhs, "machine_move_cost");
		// ipModelAsString.append(String.format("= %d", rhs));
		// log.debug("Soft Constraints4 added");
		// point.collect();
	}

	private GRBVar getxVar(int process_id, Integer machine_id) {
		// log.debug(String.format("Process:%d Machine:%d\n", process_id,
		// machine_id));
		int xvar_process_index = selectedProcesses.indexOf(new Integer(
				process_id));
		if (xvar_process_index == -1)
			return null;
		else
			return x[xvar_process_index].get(machine_id);
	}

	public Solution saveToSolution() {
		Solution solution = new Solution(problem);
		for (int p = 0; p < problem.P; p++) {
			for (int m = 0; m < problem.M; m++) {
				try {
					if (getxVar(p, m) != null) {
						double x_value = getxVar(p, m).get(GRB.DoubleAttr.X);
						if (x_value > 0.9999) {
							solution.assignProcessToMachine(p, m);
						}
					} else {
						if (current_solution.getMachineIdForProcessId(p) == m) {
							solution.assignProcessToMachine(p, m);
						}
					}
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}
		}
		return solution;
	}

	public void updateSolutionDirect(SolverEnvironment env,
			List<Integer> selectedProcesses) {
		for (Integer p : selectedProcesses) {
			Process aProcess = problem.getProcesses().get(p);
			for (Integer m : aProcess.getEligibleMachines()) {
				try {
					double x_value = getxVar(p, m).get(GRB.DoubleAttr.X);
					if (x_value > 0.9999) {
						int old_m_id = env.getPotentialSolution()
								.getMachineIdForProcessId(p);
						if (old_m_id != m) {
							env.getCostCalculator()
									.getProcessMoveToMachineCost(
											env.getPotentialSolution(), p,
											old_m_id, m);
							env.getPotentialSolution().moveProcess(p, old_m_id,
									m);
							env.getCostCalculator().updateServiceMove(p,
									old_m_id, m);
							env.getCostCalculator().updateNewSolutionCost();
						}
					}
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public void updateSolution(SolverEnvironment env,
			List<Integer> selectedProcesses) {
		for (Integer p : selectedProcesses) {
			Process aProcess = problem.getProcesses().get(p);
			for (Integer m : aProcess.getEligibleMachines()) {
				try {
					double x_value = getxVar(p, m).get(GRB.DoubleAttr.X);
					if (x_value > 0.9999) {
						int old_m_id = env.getPotentialSolution()
								.getMachineIdForProcessId(p);
						if (old_m_id != m) {
							env.getCostCalculator()
									.getProcessMoveToMachineCost(
											env.getPotentialSolution(), p,
											old_m_id, m);
							IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(
									env);
							cmd.setM_id(m);
							cmd.setP_id(p);
							env.getInvoker().addCommand(cmd);
							env.getInvoker().executeLastCommand();
							env.getCostCalculator().updateNewSolutionCost();
						}
					}
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}
		}
	}

	// PA 02122011
	public void saveToCurrentSolution() {
		for (int p = 0; p < problem.P; p++) {
			for (int m = 0; m < problem.M; m++) {
				try {
					if (getxVar(p, m) != null) {
						double x_value = getxVar(p, m).get(GRB.DoubleAttr.X);
						if (x_value > 0.9999) {
							current_solution.removeProcess(p);
							current_solution.assignProcessToMachine(p, m);
						}
					}
				} catch (GRBException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public long computeAndGetCostOverIPModel() {
		try {
			computeLoadCost();
			computeBalanceCost();
			computeProcessMoveCost();
			computeServiceMoveCost();
			computeMachineMoveCost();
		} catch (GRBException ex) {
			ex.printStackTrace();
		}
		return load_cost + balance_cost + process_move_cost + service_move_cost
				+ machine_move_cost;
	}

	public void printCostInfo() {
		try {
			load_cost = computeLoadCost();
			balance_cost = computeBalanceCost();
			process_move_cost = computeProcessMoveCost();
			service_move_cost = computeServiceMoveCost();
			machine_move_cost = computeMachineMoveCost();
			log.debug(String.format(
					"[GRB Solution=%,d] load_cost=%,d balance_cost=%,d process move cost=%,d "
							+ "service move cost=%,d machine move cost=%,d",
					(long) Math.round(model.get(GRB.DoubleAttr.ObjVal)),
					load_cost, balance_cost, process_move_cost,
					service_move_cost, machine_move_cost));
			// for (int m = 0; m < problem.M; m++)
			// for (int r = 0; r < problem.RE; r++) {
			// System.out.printf("lo_cost_m%dr%d = %.2f\n", m, r,
			// lo_cost[m][r].get(GRB.DoubleAttr.X));
			// }
			// for (int bo = 0; bo < problem.BO; bo++)
			// for (int m = 0; m < problem.M; m++) {
			// System.out.printf("ba_cost_bo%dm%d = %.2f\n", bo, m,
			// ba_cost[bo][m].get(GRB.DoubleAttr.X));
			// }
		} catch (GRBException ex) {
			ex.printStackTrace();
		}

	}

	public long[] getCostVector() {
		return new long[] { load_cost, balance_cost, process_move_cost,
				service_move_cost, machine_move_cost };
	}

	public double getSolverObjectiveValue() {
		try {
			return model.get(GRB.DoubleAttr.ObjVal);
		} catch (GRBException e) {
			log.info("Solver has not a computed objective value");
			return -1.0;
		}
	}
}
